package com.wnxy.sddfp.system.service.impl;
import java.math.BigDecimal;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;

import cn.hutool.core.util.RandomUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.wnxy.sddfp.api.bank.TradeLogApi;
import com.wnxy.sddfp.common.constant.UserConstant;
import com.wnxy.sddfp.common.exception.Assert;
import com.wnxy.sddfp.common.model.dto.bank.BankCardAddDTO;
import com.wnxy.sddfp.common.model.dto.bank.CardBindDto;
import com.wnxy.sddfp.common.model.dto.bank.SignatureDto;
import com.wnxy.sddfp.common.model.dto.user.LoginDto;
import com.wnxy.sddfp.common.model.dto.user.UserRegistDto;
import com.wnxy.sddfp.common.model.enums.PrefixEnums;
import com.wnxy.sddfp.common.model.vo.user.LoginVo;
import com.wnxy.sddfp.common.model.vo.user.UserInfoVo;
import com.wnxy.sddfp.common.result.enums.impl.BusinessCode;
import com.wnxy.sddfp.common.result.enums.impl.ResultCode;
import com.wnxy.sddfp.common.util.JwtTemplate;
import com.wnxy.sddfp.common.util.TokenUtils;
import com.wnxy.sddfp.common.util.UniqueIdGenerator;
import com.wnxy.sddfp.system.entity.User;
import com.wnxy.sddfp.system.entity.UserCard;
import com.wnxy.sddfp.system.entity.UserDetail;
import com.wnxy.sddfp.system.mapper.UserCardMapper;
import com.wnxy.sddfp.system.mapper.UserDetailMapper;
import com.wnxy.sddfp.system.mapper.UserMapper;
import com.wnxy.sddfp.system.service.IUserService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.client.RestTemplate;

/**
 * <p>
 * 用户表 服务实现类
 * </p>
 *
 * @author Jet
 * @since 2024-01-15
 */
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {

    private final UserMapper userMapper;
    private final UserDetailMapper userDetailMapper;
    private final PasswordEncoder passwordEncoder;
    private final StringRedisTemplate stringRedisTemplate;
    private final JwtTemplate jwtTemplate;
    private final UserCardMapper userCardMapper;
    private final RestTemplate restTemplate;
    private final TradeLogApi tradeLogApi;
    public UserServiceImpl(
            UserMapper userMapper,
            UserDetailMapper userDetailMapper,
            PasswordEncoder passwordEncoder, StringRedisTemplate stringRedisTemplate, JwtTemplate jwtTemplate, UserCardMapper userCardMapper, RestTemplate restTemplate, TradeLogApi tradeLogApi) {
        this.userMapper = userMapper;
        this.userDetailMapper = userDetailMapper;
        this.passwordEncoder = passwordEncoder;
        this.stringRedisTemplate = stringRedisTemplate;
        this.jwtTemplate = jwtTemplate;
        this.userCardMapper = userCardMapper;
        this.restTemplate = restTemplate;
        this.tradeLogApi = tradeLogApi;
    }

    private Long getCount(String columnName,Object columnValue) {
        return userMapper.selectCount(Wrappers.<User>query().eq(columnName,columnValue));
    }
    private User getUserByColumn(String columnName,Object columnValue) {
        return userMapper.selectOne(Wrappers.<User>query().eq(columnName,columnValue));
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW,rollbackFor = Exception.class)
    @Override
    public void regist(UserRegistDto userRegistDto) {
        // 校验手机号码 & 用户名是否存在
        Long countPhone = this.getCount("phone",userRegistDto.getPhone());
        Assert.error(countPhone > 0, BusinessCode.PHONE_EXISTED);
        Long countName = this.getCount("user_name",userRegistDto.getUserName());
        Assert.error(countName > 0, BusinessCode.USER_EXISTED);

        // 保存用户、用户详情
        saveUser(userRegistDto);
    }

    /**
     * 抽取的保存用户公用方法：注册时候、登录功能完成自动注册调用
     * @param userRegistDto
     */
    private void saveUser(UserRegistDto userRegistDto) {
        // 保存用户信息
        User user = new User();
        user.setPhone(userRegistDto.getPhone());
        user.setUserName(userRegistDto.getUserName());
        user.setPwd(passwordEncoder.encode(userRegistDto.getPwd()));
        user.setUserType(UserConstant.USER_TYPE_PERSONAL);
        user.setUserStatus(true);
        userMapper.insert(user);


        // 保存用户详情
        UserDetail userDetail = new UserDetail();
        userDetail.setId(user.getId());
        userDetail.setUserName(user.getUserName());
        userDetail.setPhone(user.getPhone());
        userDetail.setBorrowMoney(new BigDecimal("0"));
        userDetail.setUserCode(UniqueIdGenerator.generator(PrefixEnums.UC.getPrefix()));
        userDetail.setCardBindStatus(false);

        userDetailMapper.insert(userDetail);
    }

    @Override
    public LoginVo login(LoginDto loginDto) {
        return loginDto.getIsPhoneLogin() ? userPhoneLogin(loginDto) : userPwdLogin(loginDto);
    }

    /**
     * 账号密码登录
     * @param loginDto
     * @return
     */
    private LoginVo userPwdLogin(LoginDto loginDto) {
        // 根据用户名查询,如果用户名为空，返回错误
        User user = this.getUserByColumn("user_name",loginDto.getLoginName());
        Assert.error(user == null, BusinessCode.USER_LOGIN_ERROR);

        // 根据查询的密码与用户输入的密码对比,校验
        Assert.error(
                !passwordEncoder.matches(loginDto.getLoginSecret(),user.getPwd()),
                BusinessCode.USER_LOGIN_ERROR
        );

        // 返回
        return getLoginVo(user);
    }

    /**
     * 手机号码、验证码登录
     * @param loginDto
     * @return
     */
    private LoginVo userPhoneLogin(LoginDto loginDto) {
        // 校验验证码
        String code = stringRedisTemplate.opsForValue().get(loginDto.getLoginName());
        Assert.error(!loginDto.getLoginSecret().equals(code),BusinessCode.USER_VERIFY_CODE_ERROR);

        // 根据手机号码查询，如果手机号码不存在自动注册
        User user = this.getUserByColumn("phone", loginDto.getLoginName());
        if (user == null) {
            UserRegistDto registDto = new UserRegistDto();
            // 手机验证码登录，手机号码不存在，自动注册，此时用户名随机生成、密码默认123456
            registDto.setUserName(RandomUtil.randomString(6));
            registDto.setPwd("123456");
            registDto.setPhone(loginDto.getLoginName());

            // 保存用户、用户详情
            saveUser(registDto);

            // 根据手机号码查询
            user = this.getUserByColumn("phone",registDto.getPhone());
        }

        return getLoginVo(user);
    }

    private LoginVo getLoginVo(User user) {
        Map<String,Object> payload = new HashMap<>();
        payload.put("userId",user.getId());
        // 生成token
        String token = jwtTemplate.createToken(payload);
        // 存储到Redis
        stringRedisTemplate.opsForValue().set(
                UserConstant.USER_TOKEN + user.getId(),token, Duration.ofMinutes(30));
        // 返回
        return LoginVo.builder()
                .id(user.getId())
                .userName(user.getUserName())
                .phone(user.getPhone())
                .token(token)
                .build();
    }


    /**
     * 用户绑卡：
     * 1、从token中获取userId
     * 2、校验：根据用户id查询u_user_detail用户表，根据CARD_BIND_STATUS字段判断是否已经绑卡
     * 3、如果未绑卡，保存保卡信息到u_user_card表
     * 4、更新用户表：把请求参数中的用户真实姓名、身份证号码更新到u_user表中
     * 5、生成请求流水号，用来访问托管代理，并更新u_user_detail表中流水号字段REQ_CODE
     * 6、远程访问托管代理服务（暂不实现）
     */
    @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
    @Override
    public SignatureDto addBankAccount(CardBindDto cardBindDto) {
        //1、从token中获取userId   Authorization Bearer xx
        Long userId = TokenUtils.getTokenUserId();

        //2、校验：根据用户id查询u_user_detail用户表，根据CARD_BIND_STATUS字段判断是否已经绑卡
        UserDetail userDetail = userDetailMapper.selectById(userId);
        Assert.error(userDetail.getCardBindStatus(),BusinessCode.USER_CARD_ALREADY_BINDED);

        //3、如果未绑卡，保存保卡信息到u_user_card表
        //3.1 先判断是否已经绑卡
        Long ucCount = userCardMapper.selectCount(Wrappers.lambdaQuery(UserCard.class).eq(UserCard::getCardNo, cardBindDto.getCardNo()));
        Assert.error(ucCount > 0, BusinessCode.USER_CARD_ALREADY_BINDED);

        //3.2 未绑卡再插入数据
        UserCard userCard = new UserCard();
        userCard.setUserId(userId);
        userCard.setCardNo(cardBindDto.getCardNo());
        userCard.setPhone(userDetail.getPhone());
        userCard.setStatus(true);
        userCardMapper.insert(userCard);

        //4、更新用户表：把请求参数中的用户真实姓名、身份证号码更新到u_user表中
        userMapper.update(null,Wrappers.lambdaUpdate(User.class)
                .set(User::getRealName,cardBindDto.getRealName())
                .set(User::getIdentityCode,cardBindDto.getIdentityCode())
                .eq(User::getId,userId)
        );

        //5、生成请求流水号，用来访问托管代理，并更新u_user_detail表中流水号字段REQ_CODE
        String reqCode = UniqueIdGenerator.generator(PrefixEnums.REQ.getPrefix());
        userDetailMapper.update(null,Wrappers.lambdaUpdate(UserDetail.class)
                .set(UserDetail::getReqCode,reqCode)
                .eq(UserDetail::getId,userId)
        );
        //6、远程访问托管代理服务
        //远程接口地址：http://localhost:9030/trade-log/bank/access/card
        //请求方式：post
        //请求参数：BankCardAddDTO（reqCode、userId）
        //返回数据：SignatureDto
        //String url = "http://localhost:9030/trade-log/bank/access/card";
        //BankCardAddDTO bankCardAddDTO = new BankCardAddDTO();
        //bankCardAddDTO.setReqCode(reqCode);
        //bankCardAddDTO.setUserId(userId);
        //// 执行远程调用
        //ResponseEntity<SignatureDto> responseEntity =
        //        restTemplate.postForEntity(url, bankCardAddDTO, SignatureDto.class);
        //SignatureDto signatureDto = responseEntity.getBody();

        // 通过openfeign远程调用 (feign：伪装，就像调用本地方法一样调用远程方法)
        // 原理：JDK动态代理,接口代理
        BankCardAddDTO bankCardAddDTO = new BankCardAddDTO();
        bankCardAddDTO.setReqCode(reqCode);
        bankCardAddDTO.setUserId(userId);
        SignatureDto signatureDto = tradeLogApi.proxyBindCardLog(bankCardAddDTO);
        return signatureDto;
    }

    @Override
    public UserInfoVo findUserInfoVo(Long userId) {
        // 返回：姓名、身份证、userCode
        User user = this.getById(userId);
        Assert.error(user == null, ResultCode.DATA_IS_WRONG);

        UserDetail userDetail = userDetailMapper.selectById(userId);
        Assert.error(userDetail == null, ResultCode.DATA_IS_WRONG);

        UserInfoVo userInfoVo = new UserInfoVo();
        userInfoVo.setUserName(user.getUserName());
        userInfoVo.setIdentityCode(user.getIdentityCode());
        userInfoVo.setUserCode(userDetail.getUserCode());

        return userInfoVo;
    }
}












